鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起大致看過 ActiveSupport 包含了哪些 magic,今天就來看一下 ActiveSupport 的這些 magic 大概都是怎麼實作的,夠夠~

RailsGuide 在 ActiveSupport 這篇,有把每小節相關的 source code 以上圖紅框的形式提供出來,點進去連結後,會連到 Github rails v7.0.3.1 版本的 source code
其實 ActiveSupport 針對 Ruby 所強化的一些 magic method,實作上並不複雜,大部分都只是把常用的 method 用原本 Ruby 的方式實作而已,舉例來說 Rails 有提供 blank? 和 present? 的好用東東,可以檢查物件是不是空的、是不是存在的,而這兩個 method 的實作方法如下:

blank? 就只是拿原本 Ruby 的 empty? 過來改,如果該物件沒有 empty? 這個 method 的話,就改以自己本身當作判斷標準
而 present? 也是把 blank? 在反向而已
blank? 這邊的實作我覺得怪怪的,如果是 "  " 的話,"  ".empty? 還是 false 啊,那 "  ".blank? 是怎麼變成 true 的?!
我還做了個實驗,我把 String 新增一個叫做 is_blank? 的 method,用 "  ".is_blank? 出來的結果還是一樣是 false
class String
	def is_blank?
		respond_to?(:empty?) ? !!empty? : !self
	end
end
'  '.is_blank?  # false
'  '.blank?     # true
不知道我實驗哪裡錯了,希望大神知道答案的話,再麻煩留言告訴我 QQ
[微補充]
在我寫完文章之後,我問了 Rails 社群的大大,過沒多久就得到解答,感謝 Yan Jun 大神!
原來是我只有看 Object 的 blank? 但漏看了 String 的 blank?,原來除了 empty? 之外,還有多做 blank 的正規判斷

mattr_reader, mattr_writer, mattr_accessor, cattr_reader, cattr_writer, cattr_accessor
我第一次看到 mattr_* 這種酷東東,我們先從常用的 attr_accessor 出發:
class User
	attr_accessor :name, :email
end
user = User.new
user.name = 'Handsome Sam' # Handsome Sam
user.email = 'sam.ho@relacs-studio.com'
user # <User:0x00000001233f6be0 @email="sam.ho@relacs-studio.com", @name="Handsome Sam">
attr_accessor 可以讓 Class 被實體化變成 instance 後,擁有 getter 和 setter 的 attribute,相當於 attr_reader + attr_writer;而 mattr_accessor 功能就跟 attr_accessor 幾乎一樣,只是 mattr_accessor 是給 module 使用的,所以前面多加了個 m,我們直接來看官方範例:
module HairColors
	mattr_reader :hair_colors
end
HairColors.hair_colors # => nil
HairColors.class_variable_set("@@hair_colors", [:brown, :black])
HairColors.hair_colors # => [:brown, :black]
mattr_reader 讓 Module 方便擁有 class variable,同時替這個 class variable 新增 getter
再看一個例子,使用 module 的 class 也會有相對應的 instance methods
module HairColors
	mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
end
class Person
	include HairColors
end
Person.new.hair_colors # => [:brown, :black, :blonde, :red]
也有 options 可以決定要不要產生 instance method
module HairColors
	mattr_reader :hair_colors, instance_reader: false
end
class Person
	include HairColors
end
Person.new.hair_colors # => NoMethodError
讓我們來偷看一下 ActiveSupport 的 mattr_reader 是怎麼實作的:

rails/attribute_accessors.rb at v7.0.3.1 · rails/rails
整個 mattr_reader 才 20 行,其實不難理解,就是利用 ruby 有 Open Class 的特性,可以事後對 Class 進行修改,所以透過 mattr_reader 把新增的 class methods 加進去,如果有指定 instance_reader: false 或 instance_accessor: false 的話,就不新增 instance methods。
我這也是第一次看到 inquiry 的用法,覺得很酷,跟大家分享
"production".inquiry.production? # => true
"active".inquiry.inactive?       # => false
"active".inquiry.hello?          # => false
就是能像是 rails enum 會產生 enum 的 xxx? method,不過 inquiry 不太一樣,它是可以用任何字串接問號的,我們來看一下怎麼實作的

接著再去 ActiveSupport::StringInquirer 看看

respond_to_missing?
當你呼叫 str.respond_to?(:xxx) 時,如果 xxx 不是 string 有的 method 的話,那就會去執行 respond_to_missing? 這個 method,例如你去跑 '123'.respond_to?(:hello),String 沒有定義 hello 這個 method,就會去執行 respond_to_missing? 裡面的內容
method_missing
當你對 String 呼叫不存在的 method 的時候,就會去執行 method_missing 內的內容
我很好奇這個 method 的運作,所以用 Integer 做了個實驗

看 source code 又學到了神奇的東西 XD
回過頭來看一下 inquiry 的原理,其實就是把對 inquiry 呼叫的 method_name 去掉最後一個問號,再拿去跟字串本身的值做比較,於是就有了以下功能
"production".inquiry.production? # => true
"active".inquiry.inactive?       # => false
"active".inquiry.hello?          # => false
# inquiry 就是把問號前面的字,跟字串本身的字做 == 判斷而已
我對 ActiveSupport 的 source code 只有大概看過去而已,並沒有很深入、也沒有每個都看,大概整理出這三種類型
empty? → blank?, present?)mattr_accessor 是模仿原有的 attr_accessor)inquiry)最後補充,我看的這篇 RailsGuide ActiveSupport,其實只有講 core_extension 而已,也就是說還有超多 magic 沒有提到的 XD,真是由衷佩服這些貢獻者,太強了

我們明天見~